<h3>Chapter 4: The LPC Pre-Compiler</h3>
<p><strong>4.1 Review</strong></p>
<p>
The previous chapter was quite heavy, so now I will
slow down a bit so you can digest and play with mappings and arrays by taking
on the rather simple topic of the LPC pre-compiler. By this point, however,
you should well understand how the driver interacts with the mudlib and
be able to code objects which use call outs and heart beats. In addition,
you should be coding simple objects which use mappings and arrays, noting
how these data types perform in objects. It is also a good idea to start
looking in detail at the actual mudlib code that makes up your mud. See
if you understand everything which is going on in your mudlibs room and
monster codes. For things you do not understand, ask the people on your
mud designated to answer creator coding questions.
</p>
<p>
Pre-compiler is actually a bit of a misnomer since LPC code
is never truly compiled. Although this is changing with prototypes of newer
LPC drivers, LPC drivers interpret the LPC code written by creators rather
than compile it into binary format. Nevertheless, the LPC pre- compiler
functions still perform much like pre-compilers for compiled languages in
that pre-compiler directives are interpreted before the driver even starts
to look at object code.
</p>
<p>
4.2 Pre-compiler Directives If you do not know what a pre-compiler
is, you really do not need to worry. With respect to LPC, it is basically
a process which happens before the driver begins to interpret LPC code which
allows you to perform actions upon the entire code found in your file. Since
the code is not yet interpreted, the pre-compiler process is involved before
the file exists as an object and before any LPC functions or instructions
are ever examined. The pre-compiler is thus working at the file level, meaning
that it does not deal with any code in inherited files.
</p>
<p>
The pre-compiler searches a file sent to it for pre-compiler
directives. These are little instructions in the file meant only for the
pre-compiler and are not really part of the LPC language. A pre-compiler
directive is any line in a file beginning with a pound (#) sign. Pre-compiler
directives are generally used to construct what the final code of a file
will look at. The most common pre-compiler directives are:
<blockquote>
#define<br>
#undefine<br>
#include<br>
#ifdef<br>
#ifndef<br>
#if<br>
#elseif<br>
#else<br>
#endif<br>
#pragma<br>
</blockquote>
Most realm coders on muds use exclusively the directives #define
and #include. The other directives you may see often and should understand
what they mean even if you never use them.
</p>
<p>
The first pair of directives are:
<blockquote>
#define<br>
#undefine
</blockquote>
The #define directive sets up a set of characters which will be replaced
any where they exist in the code at precompiler time with their definition.
For example, take:
<blockquote>
#define OB_USER "/std/user"
</blockquote>
This directive has the pre-compiler search the entire file for instances
of OB_USER. Everywhere it sees OB_USER, it replaces with "/std/user".
Note that it does not make OB_USER a variable in the code. The LPC interpreter
never sees the OB_USER label. As stated above, the pre-compiler is a
process which takes place before code interpretation. So what you wrote
as:
</p>
<pre>
#define OB_USER "/std/user"

void create() {
    if( !file_exists( OB_USER+".c") )
        write("Merde! No user file!");
    else
        write("Good! User file still exists!");
}
</pre>
would arrive at the LPC interpreter as:
<pre>
void create() {
    if( !file_exists("/std/user"+".c") )
        write("Merde! No user file!");
    else
        write("Good! User file still exists!");
}
</pre>
<p>
Simply put, #define just literally replaces the defined label with whatever
follows it. You may also use #define in a special instance where no value
follows. This is called a binary definition. For example:
<blockquote>
#define __NIGHTMARE
</blockquote>
exists in the config file for the Nightmare Mudlib. This allows for pre-
compiler tests which will be described later in the chapter.
</p>
<p>
The other pre-compiler directive you are likely to use often is #include.
As the name implies, #include includes the contents of another file right
into the file being pre-compiled at the point in the file where the directive
is placed. Files made for inclusion into other files are often called header
files. They sometimes contain things like #define directives used by multiple
files and function declarations for the file. The traditional file extension
to header files is .h.
</p>
<p>
Include directives follow one of 2 syntax's:
<blockquote>
#include <filename>&lt;filename&gt;<filename><br>
#include "filename"
</blockquote>
</p>
<p>
If you give the absolute name of the file, then which syntax
you use is irrelevant. How you enclose the file name determines how the
pre- compiler searches for the header files. The pre-compiler first searches
in system include directories for files enclosed in <>. For files enclosed
in "", the pre-compiler begins its search in the same directory as the file
going through the pre-compiler. Either way, the pre-compiler will search
the system include directories and the directory of the file for the header
file before giving up. The syntax simply determines the order.
</p>
<p>
The simplest pre-compiler directive is the #pragma directive. It is doubtful
you will ever use this one. Basically, you follow the directive with some
keyword which is meaningful to your driver. The only keyword I have ever
seen is strict_types, which simply lets the driver know you want this file
interpreted with strict data typing. I doubt you will ever need to use this,
and you may never even see it. I just included it in the list in the event
you do see it so you do not think it is doing anything truly meaningful.
</p>
<p>
The final group of pre-compiler directives are the conditional
pre-compiler directives. They allow you to pre-compile the file one way
given the truth value of an expression, otherwise pre-compile the file another
way. This is mostly useful for making code portable among mudlibs, since
putting the m_delete() efun in code on a MudOS mud would normally cause
an error, for example. So you might write the following:
<blockquote>
#ifdef MUDOS<br>
map_delete(map, key);<br>
#else<br>
map = m_delete(map, key);<br>
#endif
</blockquote>
which after being passed through the pre-compiler will appear to the
interpreter as:
<blockquote>
map_delete(map, key);
</blockquote>
on a MudOS mud, and:
<blockquote>
map = m_delete(map, key);
</blockquote>
on other muds. The interpreter never sees the function call that would
cause it to spam out in error.
</p>
<p>
Notice that my example made use of a binary definition as described above.
Binary definitions allow you to pass certain code to the interpreter based
on what driver or mudlib you are using, among other conditions.
</p>
<hr size="1">
<p><strong>4.3 Summary</strong></p>
<p>The pre-compiler is a useful LPC tool for maintaining modularity
among your programs. When you have values that might be subject to change,
but are used widely throughout your files, you might stick all of those
values in a header file as #define statements so that any need to make a
future change will cause you to need to change just the #define directive.
A very good example of where this would be useful would be a header file
called money.h which includes the directive: #define HARD_CURRENCIES ({
"gold", "platinum", "silver", "electrum", "copper" }) so that if ever you
wanted to add a new hard currency, you only need change this directive in
order to update all files needing to know what the hard currencies are.
</p>
<p>
The LPC pre-compiler also allows you to write code which can
be ported without change among different mudlibs and drivers. Finally, you
should be aware that the pre-compiler only accepts lines ending in carriage
returns. If you want a multiple line pre-compiler directive, you need to
end each incomplete line with a backslash(\).
</p>
